﻿using System;
using System.Collections.Generic;

using FirstFloor.Reversi.Controls;

namespace FirstFloor.Reversi
{
    /// <summary>
    /// Implements the Reversi game rules.
    /// </summary>
    public class ReversiGame
    {
        private Board board;
        private Dictionary<PieceColor, List<Location>> validMoves;

        /// <summary>
        /// Initializes a new instance of the <see cref="ReversiGame"/> class.
        /// </summary>
        /// <param name="board">The board.</param>
        public ReversiGame(Board board)
        {
            if (board == null) {
                throw new ArgumentNullException("board");
            }

            this.board = board;
            this.validMoves = new Dictionary<PieceColor, List<Location>>();

            InitializeGame();
        }

        private List<Location> GetAdjacentLocationsHavingColor(int x, int y, PieceColor color)
        {
            List<Location> locations = new List<Location>();

            for (int i = Math.Max(0, x - 1); i < Math.Min(Board.BoardLength, x + 2); i++) {
                for (int j = Math.Max(0, y - 1); j < Math.Min(Board.BoardLength, y + 2); j++) {
                    if (!(x == i && y == j) && this.board[i, j] == color) {
                        locations.Add(new Location(i, j));
                    }
                }
            }
            return locations;
        }

        private bool CalculateValidMove(int x, int y, PieceColor color)
        {
            PieceColor oppositeColor = GetOppositeColor(color);
            List<Location> locations = GetAdjacentLocationsHavingColor(x, y, oppositeColor);

            foreach(Location location in locations){
                int deltaX = location.X - x;
                int deltaY = location.Y - y;

                int nextX = x + deltaX;
                int nextY = y + deltaY;

                while (nextX > -1 && nextX < Board.BoardLength && nextY > -1 && nextY < Board.BoardLength) {
                    PieceColor locationColor = this.board[nextX, nextY];

                    if (locationColor == PieceColor.None) {
                        break;
                    }
                    else if (locationColor == oppositeColor) {
                        nextX += deltaX;
                        nextY += deltaY;
                    }
                    else {
                        return true;
                    }
                }
            }

            return false;
        }

        private void CalculateValidMoves()
        {
            this.validMoves.Clear();

            PieceColor[] colors = new PieceColor[] { PieceColor.Red, PieceColor.White };
            foreach (PieceColor color in colors) {
                this.validMoves.Add(color, new List<Location>());
            }

            for (int x = 0; x < Board.BoardLength; x++) {
                for (int y = 0; y < Board.BoardLength; y++) {
                    if (this.board[x, y] == PieceColor.None) {
                        foreach (PieceColor color in colors) {
                            if (CalculateValidMove(x, y, color)) {
                                this.validMoves[color].Add(new Location(x, y));
                            }
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Initializes the game.
        /// </summary>
        public void InitializeGame()
        {
            this.board.Clear();
            this.board[3, 3] = PieceColor.Red;
            this.board[3, 4] = PieceColor.White;
            this.board[4, 3] = PieceColor.White;
            this.board[4, 4] = PieceColor.Red;

            CalculateValidMoves();
        }

        /// <summary>
        /// Gets the opposite color.
        /// </summary>
        /// <param name="color">The color.</param>
        /// <returns></returns>
        public PieceColor GetOppositeColor(PieceColor color)
        {
            if (color == PieceColor.Red) {
                return PieceColor.White;
            }
            else if (color == PieceColor.White) {
                return PieceColor.Red;
            }
            else {
                throw new ArgumentOutOfRangeException("color");
            }
        }

        /// <summary>
        /// Marks the valid moves for specified color.
        /// </summary>
        /// <param name="color">The color.</param>
        public void MarkValidMoves(PieceColor color)
        {
            List<Location> validMoves = this.validMoves[color];

            this.board.ClearMarks();
            foreach (Location validMove in validMoves) {
                this.board.MarkPiece(validMove.X, validMove.Y, true);
            }
        }

        /// <summary>
        /// Sets the valid moves for specified color selectable.
        /// </summary>
        /// <param name="color">The color.</param>
        public void SetValidMovesSelectable(PieceColor color)
        {
            List<Location> validMoves = this.validMoves[color];

            this.board.ClearSelectables();
            foreach (Location validMove in validMoves) {
                this.board.SetSelectable(validMove.X, validMove.Y, true);
            }
        }

        /// <summary>
        /// Determines whether specified move is valid.
        /// </summary>
        /// <param name="x">The x.</param>
        /// <param name="y">The y.</param>
        /// <param name="color">The color.</param>
        /// <returns>
        /// 	<c>true</c> if [is valid move] [the specified x]; otherwise, <c>false</c>.
        /// </returns>
        public bool IsValidMove(int x, int y, PieceColor color)
        {
            List<Location> moves = this.validMoves[color];
            foreach (Location move in moves) {
                if (move.X == x && move.Y == y) {
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// Determines whether the game is over.
        /// </summary>
        /// <returns>
        /// 	<c>true</c> if the game is over; otherwise, <c>false</c>.
        /// </returns>
        public bool IsGameOver()
        {
            return this.validMoves[PieceColor.Red].Count == 0 && this.validMoves[PieceColor.White].Count == 0;
        }

        /// <summary>
        /// Determines whether specified color can move.
        /// </summary>
        /// <param name="color">The color.</param>
        /// <returns>
        /// 	<c>true</c> if [has valid moves] [the specified color]; otherwise, <c>false</c>.
        /// </returns>
        public bool CanMove(PieceColor color)
        {
            return this.validMoves[color].Count > 0;
        }

        /// <summary>
        /// Moves a piece at specified position.
        /// </summary>
        /// <param name="x">The x.</param>
        /// <param name="y">The y.</param>
        /// <param name="color">The color.</param>
        public void MovePiece(int x, int y, PieceColor color)
        {
            if (IsValidMove(x, y, color)) {
                this.board.ClearMarksAndSelectables();

                PieceColor oppositeColor = GetOppositeColor(color);
                List<Location> locations = GetAdjacentLocationsHavingColor(x, y, oppositeColor);

                foreach (Location location in locations) {
                    int deltaX = location.X - x;
                    int deltaY = location.Y - y;
                    List<Location> newLocations = new List<Location>();

                    int nextX = x + deltaX;
                    int nextY = y + deltaY;

                    while (nextX > -1 && nextX < Board.BoardLength && nextY > -1 && nextY < Board.BoardLength) {
                        PieceColor locationColor = this.board[nextX, nextY];

                        if (locationColor == PieceColor.None) {
                            break;
                        }
                        else if (locationColor == oppositeColor) {
                            newLocations.Add(new Location(nextX, nextY));
                            nextX += deltaX;
                            nextY += deltaY;
                        }
                        else {
                            foreach (Location newLocation in newLocations) {
                                this.board[newLocation.X, newLocation.Y] = color;
                            }
                            break;
                        }
                    }
                }

                this.board[x, y] = color;
            }

            CalculateValidMoves();
        }
    }
}
